package practica1.manejoExperimento;

import beancomentarios.Comentario;
import beancomentarios.ModeloComentarios;
import es.miguelgonzalez.jgraficacomida.GraficaComidaIncorrectValueException;
import es.miguelgonzalez.jgraficacomida.JGraficaComidaModel;
import java.io.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import practica1.Practica1;
import practica1.domain.SimulacionBacterias;
import practica1.language.Language;
import practica1.logic.LogicExperimento;
import practica1.logic.LogicPoblacion;
import practica1.ui.tablaPoblacion.ModeloPoblacionTabla;

/**
 * Clase que permite manejar un experimento en disco duro
 * @author Miguel González - Ceura
 */
public class ManejoExperimento {
    private static ArrayList<ManejoExperimentoListener> listeners;
    private static final String DBURL;
    private static final String SCHEMA;
    
    static {
        Properties prop = new Properties();
        InputStream is = null;

        try {
            is=new FileInputStream(ManejoExperimento.class.getResource(
                    "PropertiesConexion.properties").getFile());
            prop.load(is);
            
            
        } catch (FileNotFoundException ex) {
            Practica1.log.error("No se pudo cargar el fichero de propiedades de BD");
        } catch (IOException ex) {
            Practica1.log.error("No se pudo cargar el fichero de propiedades de BD");
        }
        
        DBURL = prop.getProperty("DBURL");
        SCHEMA = prop.getProperty("SCHEMA");
        
        listeners = new ArrayList<ManejoExperimentoListener>();
        
        try {
                Class.forName("org.apache.derby.jdbc.ClientDriver").newInstance();    
        } catch (ClassNotFoundException ex) {
            Practica1.log.error("Error registrando driver derby", ex);
        } catch (InstantiationException ex) {
            Practica1.log.error("Error registrando driver derby", ex);
        } catch (IllegalAccessException ex) {
            Practica1.log.error("Error registrando driver derby", ex);
        }
    }
    
    /**
     * Método estático que permite abrir un experimento del disco duro
     * @param fichExperimento Fichero del experimento a abrir
     * @return LogicExperimento
     * @throws ExperimentoInvalidoException Lanza un error si no se pudo abrir
     * el experimento por tener un formato inválido
     */
    public static void abrirExperimento(File fichExperimento) {
        AbrirHilo abrirHilo = new AbrirHilo(fichExperimento);
        abrirHilo.start();
    }
    
    public static void abrirExperimento(String nombreExperimento) {
        AbrirExperimentoBD abrirHilo = new AbrirExperimentoBD(nombreExperimento);
        abrirHilo.start();
    }
    
    public static ArrayList<String> obtenerExperimentosBD() {
        ArrayList<String> exp = new ArrayList<String>();
        
        Connection con = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            con = DriverManager.getConnection(DBURL);
            
            stmt = con.createStatement();
            
            rs = stmt.executeQuery("SELECT NOMBREEXPERIMENTO FROM " + SCHEMA +
                    ".EXPERIMENTO");
            
            while(rs.next()) {
                exp.add(rs.getString("NOMBREEXPERIMENTO"));
            }
        } catch (SQLException ex) {
            Practica1.log.error("Error SQL", ex);
        } finally {
            if(rs != null) {
                try {
                    rs.close();
                } catch (SQLException ex) {
                    Practica1.log.error("Error SQL", ex);
                }
            }
            
            if(stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    Practica1.log.error("Error SQL", ex);
                }
            }
            
            if(con != null) {
                try {
                    con.close();
                } catch (SQLException ex) {
                    Practica1.log.error("Error SQL", ex);
                }
            }
        }
        
        return exp;
    }
    
    
    
    /**
     * Clase estática que permite guardar un expGuardar en memoria
     * @param expGuardar LogicExperimento a guardar
     * @throws ExperimentoInvalidoException Si hay un problema al guardar el
     * expGuardar se provoca el error
     */
    public static void guardarExperimento(LogicExperimento experimento) {
        if(experimento.isGuardadoBD()) {
            GuardarExperimentoBD guardarHilo = new GuardarExperimentoBD(
                experimento);
            guardarHilo.start();   
        } else {
            GuardarHilo guardarHilo = new GuardarHilo(experimento);
            guardarHilo.start();
        }
    }
    
    public static void guardarExperimento(LogicExperimento experimento, 
            ManejoExperimentoListener listener) {
        if(experimento.isGuardadoBD()) {
            GuardarExperimentoBD guardarHilo = new GuardarExperimentoBD(
                    experimento, listener);
            guardarHilo.start();
        } else {
            GuardarHilo guardarHilo = new GuardarHilo(experimento, listener);
            guardarHilo.start();
        }
    }
    
    public static void addManejoExperimentoListener(ManejoExperimentoListener l) {
        if(l != null) {
            listeners.add(l);
        }
    }
    
    public static void removeManejoExperimentoListener(ManejoExperimentoListener l) {
        if(l != null) {
            listeners.remove(l);
        }
    }
    
    private synchronized static void fireErrorAbriendoExperimento(final String rutaExp) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                for(ManejoExperimentoListener l : listeners) {
                    l.errorAbrirExperimento(rutaExp);
                }
            }
        });
    }
    
    private synchronized static void fireAbriendoExperimento(final String rutaExp) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                for(ManejoExperimentoListener l : listeners) {
                    l.abriendoExperimento(rutaExp);
                }
            }
        });
    }
    
    private synchronized static void fireAbiertoExperimento(final LogicExperimento fichExperimento) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                for(ManejoExperimentoListener l : listeners) {
                    l.abiertoExperimento(fichExperimento);
                }
            }
        });
    }
        
    public static boolean isConectado() {
        Connection con = null;
        boolean conectado = true;
        try {
            con = DriverManager.getConnection(DBURL);
        } catch(SQLException ex) {
            Practica1.log.error("Comprobando si hay conexión en isConectado" , ex);
            conectado = false;
        } finally {
            if(con != null) {
                try {
                    con.close();
                } catch (SQLException ex) {
                    Practica1.log.error("Comprobando si hay conexión en isConectado" , ex);
                }
            }
        }
        
        return conectado;
    }
    
    private static class AbrirExperimentoBD extends Thread {
        
        private String nombreEx;
        
        public AbrirExperimentoBD(String nombreEx) {
            this.nombreEx = nombreEx;
        }
        
        @Override
        public void run() {
            fireAbriendoExperimento(nombreEx);
            
            LogicExperimento exp;
            
            Connection con = null;
            Statement sExp = null;
            Statement sPob = null;
            Statement sTab = null;
            Statement sCom = null;
            ResultSet rsExp = null;
            ResultSet rsPob = null;
            ResultSet rsTab = null;
            ResultSet rsCom = null;
            
            try {
                con = DriverManager.getConnection(DBURL);
                
                sExp = con.createStatement();
                sPob = con.createStatement();
                sTab = con.createStatement();
                sCom = con.createStatement();
                
                rsExp = sExp.executeQuery("SELECT NOMBREEXPERIMENTO, TIPOBACTERIA,"
                        + " NOMBREINVESTIGADOR, PROYECTOINVESTIGACION,"
                        + " NUMDIASEXPERIMENTO, LIMITEALIMENTACIONMAXIMA,"
                        + " UNIDADESMEDIDA FROM " + SCHEMA + ".EXPERIMENTO WHERE"
                        + " NOMBREEXPERIMENTO = '" + nombreEx + "'");
                
                String tipoBacteria;
                String nombreInvestigador;
                String proyectoInvestigacion;
                int numDiasExperimento;
                int limiteAlimentacionMax;
                String unidadesMedida;
                
                
                if(rsExp.next()) {
                    tipoBacteria = rsExp.getString("TIPOBACTERIA");
                    nombreInvestigador = rsExp.getString("NOMBREINVESTIGADOR");
                    proyectoInvestigacion = rsExp.getString("PROYECTOINVESTIGACION");
                    numDiasExperimento = rsExp.getInt("NUMDIASEXPERIMENTO");
                    limiteAlimentacionMax = rsExp.getInt("LIMITEALIMENTACIONMAXIMA");
                    unidadesMedida = rsExp.getString("UNIDADESMEDIDA");
                    
                } else {
                    throw new SQLException("No existe el experimento " +
                            nombreEx + " en base de datos");
                }
                
                //Creamos el experimento a partir de los datos de la BD
                exp = new LogicExperimento(new File(System.getProperty("user.home")),
                        nombreEx, tipoBacteria, nombreInvestigador,
                        proyectoInvestigacion, numDiasExperimento, 
                        limiteAlimentacionMax, unidadesMedida, true);
                
                //Ahora obtenemos las poblaciones
                rsPob = sPob.executeQuery("SELECT NOMBREPOBLACION, FECHA,"
                        + " TAMANIOPOBLACION, TEMPERATURA, NUMDIAS,"
                        + " ESCALATEMPERATURA, SINCRONIZARCOMIDA, ALIMENTACIONINI,"
                        + " DIAMAXIMA, ALIMENTACIONMAX, ALIMENTACIONFIN FROM "
                        + SCHEMA + ".POBLACION WHERE NOMBREEXPERIMENTO = '"
                        + nombreEx + "'");
                
                
                while(rsPob.next()) {
                    String nombrePob = rsPob.getString("NOMBREPOBLACION");
                    String fecha = rsPob.getString("FECHA");
                    int tamanio = rsPob.getInt("TAMANIOPOBLACION");
                    int temperatura = rsPob.getInt("TEMPERATURA");
                    int numDias = rsPob.getInt("NUMDIAS");
                    String escalaTemp = rsPob.getString("ESCALATEMPERATURA");
                    boolean sincronizarCom = rsPob.getBoolean("SINCRONIZARCOMIDA");
                    int alimentacionIni = rsPob.getInt("ALIMENTACIONINI");
                    int diaMax = rsPob.getInt("DIAMAXIMA");
                    int alimentacionMax = rsPob.getInt("ALIMENTACIONMAX");
                    int alimentacionFin = rsPob.getInt("ALIMENTACIONFIN");
                    
                    LogicPoblacion p = new LogicPoblacion(nombrePob, exp);
                    p.setFecha(fecha);
                    p.setTamanioPoblacion(tamanio);
                    p.setTemperatura(temperatura);
                    p.setNumDias(numDias);
                    p.setEscalaTemperatura(escalaTemp);
                    p.setSincronizarComida(sincronizarCom);
                    
                    JGraficaComidaModel modelGrafica = new JGraficaComidaModel(numDias,
                            0, limiteAlimentacionMax);
                    modelGrafica.setAlimentoInicial(0);
                    modelGrafica.setAlimentoFinal(0);
                    modelGrafica.setAlimentoMax(alimentacionMax);
                    modelGrafica.setDiaMax(diaMax);
                    modelGrafica.setAlimentoInicial(alimentacionIni);
                    modelGrafica.setAlimentoFinal(alimentacionFin);
                    p.setModeloGraficaComida(modelGrafica);
                    
                    String[][] datos = new String[numDias][];
                    String []cabeceras = new String[]{
                        Language.getI().getP("DIA"),
                        Language.getI().getP("TAMANIO_POBLACION"),
                        Language.getI().getP("BACTERIAS_MUERTAS"),
                        Language.getI().getP("TEMPERATURA"),
                        Language.getI().getP("DOSIS_COMIDA")};
                    
                    //Obtenemos los datos de la tabla de la población
                    rsTab = sTab.executeQuery("SELECT NUMDIA, POBLACION,"
                            + " BACTERIASMUERTAS, TEMPERATURA, COMIDADIA FROM "
                            + SCHEMA + ".DATOSTABLA WHERE NOMBREEXPERIMENTO = "
                            + "'" + nombreEx + "' AND NOMBREPOBLACION = "
                            + "'" + nombrePob + "'");
                    
                    while(rsTab.next()) {
                        int dia = rsTab.getInt("NUMDIA");
                        int poblacion = rsTab.getInt("POBLACION");
                        int bacMuertas = rsTab.getInt("BACTERIASMUERTAS");
                        int temp = rsTab.getInt("TEMPERATURA");
                        int comidaDia = rsTab.getInt("COMIDADIA");
                        
                        datos[dia-1] = new String[]{"" + dia,
                            (poblacion != 0) ? poblacion + "" : "",
                            (bacMuertas != 0) ? bacMuertas + "": "",
                            (temp != 0) ? temp + "" : "",
                            "" + comidaDia};
                    }
                    
                    ModeloPoblacionTabla modelTabla = 
                            new ModeloPoblacionTabla(datos, cabeceras);
                    p.setModeloPoblacionTabla(modelTabla);
                    
                    //Obtenemos los comentarios de la población
                    ModeloComentarios modelComentarios = new ModeloComentarios();
                    
                    rsCom = sCom.executeQuery("SELECT FECHACOMENTARIO, COMENTARIO"
                            + " FROM " + SCHEMA + ".COMENTARIOS WHERE NOMBREEXPERIMENTO = "
                            + "'" + nombreEx + "' AND NOMBREPOBLACION = "
                            + "'" + nombrePob + "'");
                    
                    while(rsCom.next()) {
                        String fech = rsCom.getString("FECHACOMENTARIO");
                        String com = rsCom.getString("COMENTARIO");
                        
                        modelComentarios.addComentario(new Comentario(com, fech));
                    }
                    
                    p.setModeloComentarios(modelComentarios);
                    
                    exp.addPoblacion(p);
                }
                
                //Notificamos que hemos abierto el experimento
                exp.setModified(false);
                fireAbiertoExperimento(exp);
            } catch (GraficaComidaIncorrectValueException ex) {
                Practica1.log.error("Error creando modelo de la gráfica comida", ex);
                fireErrorAbriendoExperimento(nombreEx);
            } catch (SQLException ex) {
                Practica1.log.error("Error SQL", ex);
                fireErrorAbriendoExperimento(nombreEx);
            } finally {
                if(rsCom != null) {
                    try {
                        rsCom.close();
                    } catch (SQLException ex) {
                        Logger.getLogger(ManejoExperimento.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                
                if(sCom != null) {
                    try {
                        sCom.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorAbriendoExperimento(nombreEx);
                    }
                }
                
                if(rsTab != null) {
                    try {
                        rsTab.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorAbriendoExperimento(nombreEx);
                    }
                }
                
                if(sTab != null) {
                    try {
                        sTab.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorAbriendoExperimento(nombreEx);
                    }
                }
                
                if(rsPob != null) {
                    try {
                        rsPob.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorAbriendoExperimento(nombreEx);
                    }
                }
                
                if(sPob != null) {
                    try {
                        sPob.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorAbriendoExperimento(nombreEx);
                    }
                }
                
                if(rsExp != null) {
                    try {
                        rsExp.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorAbriendoExperimento(nombreEx);
                    }
                }
                
                if(sExp != null) {
                    try {
                        sExp.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorAbriendoExperimento(nombreEx);
                    }
                }
                
                if(con != null) {
                    try {
                        con.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorAbriendoExperimento(nombreEx);
                    }
                }
            }
        }
    }
    
    private static class GuardarExperimentoBD extends Thread {
        
        private LogicExperimento experimento;
        private ManejoExperimentoListener listener;
        
        public GuardarExperimentoBD(LogicExperimento experimento) {
            this.experimento = experimento;
            listener = null;
        }
        
        public GuardarExperimentoBD(LogicExperimento experimento,
                ManejoExperimentoListener listener) {
            this.experimento = experimento;
            this.listener = listener;
        }
        
        private void borrarExperimento(LogicExperimento exp, Connection con)
                throws SQLException {
            Statement sExp = null;
            Statement sPob = null;
            Statement sTab = null;
            Statement sCom = null;
            
            try {
                sExp = con.createStatement();
                sPob = con.createStatement();
                sTab = con.createStatement();
                sCom = con.createStatement();

                sExp.executeUpdate("DELETE FROM " + SCHEMA + ".EXPERIMENTO WHERE "
                        + "NOMBREEXPERIMENTO = '" + exp.getNombreExperimento() + "'");
                
                for(LogicPoblacion p : exp.getPoblaciones()) {
                    sTab.executeUpdate("DELETE FROM " + SCHEMA + ".DATOSTABLA WHERE" +
                        " NOMBREEXPERIMENTO = '" + exp.getNombreExperimento() +
                        "' AND NOMBREPOBLACION = '" + p.getNombrePoblacion() + "'");
                    
                    sCom.executeUpdate("DELETE FROM " + SCHEMA + ".COMENTARIOS WHERE" +
                        " NOMBREEXPERIMENTO = '" + exp.getNombreExperimento() +
                        "' AND NOMBREPOBLACION = '" + p.getNombrePoblacion() + "'");
                }
                
                //Borro todas las poblaciones para ese experimento
                sPob.executeUpdate("DELETE FROM " + SCHEMA + ".POBLACION WHERE" +
                        " NOMBREEXPERIMENTO = '" + exp.getNombreExperimento() +"'");
                
            } finally {
                if(sExp != null) {
                    sExp.close();
                }
                
                if(sPob != null) {
                    sPob.close();
                }
                
                if(sTab != null) {
                    sTab.close();
                }
                
                if(sCom != null) {
                    sCom.close();
                }
            }
        }
        
        @Override
        public void run() {
            fireGuardandoExperimento(experimento);

            if(listener != null) {
                fireGuardandoExperimento(experimento, listener);
            }
            
            LogicExperimento expGuardar = experimento.clone();
            
            
            Connection con = null;
            PreparedStatement psExp = null;
            PreparedStatement psPob = null;
            PreparedStatement psTab = null;
            PreparedStatement psCom = null;
            
            try {
                con = DriverManager.getConnection(DBURL); 
                con.setAutoCommit(false);
                
                borrarExperimento(expGuardar, con);
                
                psExp = con.prepareStatement("INSERT INTO " + SCHEMA + ".EXPERIMENTO"
                        + " (NOMBREEXPERIMENTO, TIPOBACTERIA, NOMBREINVESTIGADOR,"
                        + " PROYECTOINVESTIGACION, NUMDIASEXPERIMENTO, "
                        + " LIMITEALIMENTACIONMAXIMA, UNIDADESMEDIDA) VALUES"
                        + " (?, ?, ?, ?, ?, ?, ?)");
                
                
                psExp.setString(1, expGuardar.getNombreExperimento());
                psExp.setString(2, expGuardar.getTipoBacteria());
                psExp.setString(3, expGuardar.getNombreInvestigador());
                psExp.setString(4, expGuardar.getProyectoInvestigacion());
                psExp.setInt(5, expGuardar.getNumDiasExperimento());
                psExp.setInt(6, expGuardar.getLimiteAlimentacionMaxima());
                psExp.setString(7, expGuardar.getUnidadesMedida());
                
                psExp.addBatch();
                
                psPob = con.prepareStatement("INSERT INTO " + SCHEMA + ".POBLACION("
                        + "NOMBREEXPERIMENTO, NOMBREPOBLACION, FECHA,"
                        + " TAMANIOPOBLACION, TEMPERATURA, NUMDIAS,"
                        + " ESCALATEMPERATURA, SINCRONIZARCOMIDA, ALIMENTACIONINI,"
                        + " DIAMAXIMA, ALIMENTACIONMAX, ALIMENTACIONFIN) "
                        + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
                
                psTab = con.prepareStatement("INSERT INTO " + SCHEMA + ".DATOSTABLA("
                        + "NOMBREEXPERIMENTO, NOMBREPOBLACION, NUMDIA, "
                        + "POBLACION, BACTERIASMUERTAS, TEMPERATURA, COMIDADIA) "
                        + "VALUES(?, ?, ?, ?, ?, ?, ?)");
                
                psCom = con.prepareStatement("INSERT INTO " + SCHEMA + ".COMENTARIOS("
                        + "NOMBREEXPERIMENTO, NOMBREPOBLACION, FECHACOMENTARIO,"
                        + " COMENTARIO) VALUES(?, ?, ?, ?)");
                
                //Guardamos las poblaciones
                for(LogicPoblacion p : expGuardar.getPoblaciones()) {
                    JGraficaComidaModel m = p.getModeloGraficaComida();
                    ModeloPoblacionTabla mT = p.getModeloPoblacionTabla();
                    ModeloComentarios mC = p.getModeloComentarios();
                    
                    psPob.setString(1, expGuardar.getNombreExperimento());
                    psPob.setString(2, p.getNombrePoblacion());
                    psPob.setString(3, p.getFecha());
                    psPob.setInt(4, p.getTamanioPoblacion());
                    psPob.setInt(5, p.getTemperatura());
                    psPob.setInt(6, p.getNumDias());
                    psPob.setString(7, p.getEscalaTemperatura());
                    psPob.setBoolean(8, p.isSincronizarComida());
                    psPob.setInt(9, m.getAlimentoInicial());
                    psPob.setInt(10, m.getDiaMax());
                    psPob.setInt(11, m.getAlimentoMax());
                    psPob.setInt(12, m.getAlimentoFinal());
                    
                    psPob.addBatch();
                    
                    for(int i=0; i<mT.getRowCount(); i++) {
                        psTab.setString(1, expGuardar.getNombreExperimento());
                        psTab.setString(2, p.getNombrePoblacion());
                        psTab.setInt(3, Integer.parseInt(
                                mT.getValueAt(i, 0).toString()));
                        if(mT.getValueAt(i, 1).toString().isEmpty()) {
                            psTab.setInt(4, 0);
                        } else {
                            psTab.setInt(4, Integer.parseInt(
                                    mT.getValueAt(i, 1).toString()));
                        }
                        if(mT.getValueAt(i, 2).toString().isEmpty()) {
                            psTab.setInt(5, 0);
                        } else {
                            psTab.setInt(5, Integer.parseInt(
                                    mT.getValueAt(i, 2).toString()));
                        }
                        if(mT.getValueAt(i, 3).toString().isEmpty()) {
                            psTab.setInt(6, 0);
                        } else {
                            psTab.setInt(6, Integer.parseInt(
                                    mT.getValueAt(i, 3).toString()));
                        }
                        if(mT.getValueAt(i, 4).toString().isEmpty()) {
                            psTab.setInt(7, 0);
                        } else {
                            psTab.setInt(7, Integer.parseInt(
                                    mT.getValueAt(i, 4).toString()));
                        }
                        
                        psTab.addBatch();
                    }
                    
                    for(Comentario c : mC.getComentarios()) {
                        psCom.setString(1, expGuardar.getNombreExperimento());
                        psCom.setString(2, p.getNombrePoblacion());
                        psCom.setString(3, c.getFecha());
                        psCom.setString(4, c.getTexto());
                        
                        psCom.addBatch();
                    }
                }
                
                psExp.executeBatch();
                psExp.clearBatch();
                
                psPob.executeBatch();
                psPob.clearBatch();
                
                psTab.executeBatch();
                psTab.clearBatch();
                
                psCom.executeBatch();
                psCom.clearBatch();
            
                con.commit();
                
                experimento.setModified(false);
                
                fireGuardadoExperimento(experimento);
                
                if(listener != null) {
                    fireGuardadoExperimento(experimento, listener);
                }
            } catch (SQLException ex) {
                Practica1.log.error("Error SQL", ex);
                fireErrorGuardadoExperimento(experimento);
               
                if(listener != null) {
                    fireErrorGuardadoExperimento(experimento, listener);
                }
                
                if(con != null) {
                    try {
                        con.rollback();
                    } catch (SQLException ex2) {
                        Practica1.log.error("Error SQL", ex2);
                        fireErrorGuardadoExperimento(experimento);

                        if(listener != null) {
                            fireErrorGuardadoExperimento(experimento, listener);
                        }
                    }
                }
            } finally {
                if(psExp != null) {
                    try {
                        psExp.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorGuardadoExperimento(experimento);

                        if(listener != null) {
                            fireErrorGuardadoExperimento(experimento, listener);
                        }
                    }
                }
                
                if(psPob != null) {
                    try {
                        psPob.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorGuardadoExperimento(experimento);

                        if(listener != null) {
                            fireErrorGuardadoExperimento(experimento, listener);
                        }
                    }
                }
                
                if(psTab != null) {
                    try {
                        psTab.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorGuardadoExperimento(experimento);

                        if(listener != null) {
                            fireErrorGuardadoExperimento(experimento, listener);
                        }
                    }
                }
                
                if(psCom != null) {
                    try {
                        psCom.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorGuardadoExperimento(experimento);

                        if(listener != null) {
                            fireErrorGuardadoExperimento(experimento, listener);
                        }
                    }
                }
                
                if(con != null) {
                    try {
                        con.close();
                    } catch (SQLException ex) {
                        Practica1.log.error("Error SQL", ex);
                        fireErrorGuardadoExperimento(experimento);

                        if(listener != null) {
                            fireErrorGuardadoExperimento(experimento, listener);
                        }
                    }
                }
            }
        }
    }
    
    private static class AbrirHilo extends Thread {
        private File fichExperimento;
        
        private Document xmlDoc = null;
        private Element elementoCabecera = null;

        private int numDiasExperimento;
        private int limiteAlimentacionMaxima;
        
        public AbrirHilo(File fichExperimento) {
            this.fichExperimento = fichExperimento;
        }
        
        @Override
        public void run() {
            fireAbriendoExperimento(fichExperimento.getName());
            
            String nombreExperimento = "";
            String tipoBacteria = "";
            String nombreInvestigador = "";
            String proyectoInvestigacion = "";
            //Valores por defecto por si es un fichero antiguo que no tiene
            //los nuevos valores guardados en el XML
            numDiasExperimento = 30;
            limiteAlimentacionMaxima = 300;
            String unidadesMedida = "miligramos";


            //Construimos el DOM con las preferencias
            DocumentBuilderFactory dbFactory;
            DocumentBuilder docBuilder;

            LogicExperimento experimento = null;

            try
            {
                dbFactory = DocumentBuilderFactory.newInstance();
                docBuilder = dbFactory.newDocumentBuilder();
                if(fichExperimento.exists()) {
                    xmlDoc  = docBuilder.parse(fichExperimento);
                    elementoCabecera = xmlDoc.getDocumentElement();
                } else {
                    Practica1.log.error("Abrir, no existe el experimento");
                    fireErrorAbriendoExperimento(fichExperimento.getName());
                }

                //Buscamos las propiedades del experimento
                NodeList lstPropiedades = elementoCabecera.
                        getElementsByTagName("propiedades-experimento");


                boolean propiedadesCorrecto = true;
                if(lstPropiedades.getLength() == 1) {
                    Node nodePropiedades = lstPropiedades.item(0);

                    //Obtenemos las propiedades del experimento
                    NodeList lstValores = nodePropiedades.getChildNodes();

                    //Recorremos las propiedades
                    for(int i=0; i<lstValores.getLength() && propiedadesCorrecto; i++) {
                        Node nodoPropExp = lstValores.item(i);
                        if(nodoPropExp.getNodeType() == Node.ELEMENT_NODE) {

                            String nProp = nodoPropExp.getNodeName();

                            //Comprobamos en orden que se cumplen las propiedades esperadas
                            switch(i) {
                                case 1: //Nombre experimento
                                    if(nProp.equals("nombre-experimento")) {
                                        nombreExperimento = nodoPropExp.
                                                getTextContent();
                                    } else {
                                        propiedadesCorrecto = false;
                                        Practica1.log.error("Abrir, nombre experimento inválido");
                                        fireErrorAbriendoExperimento(fichExperimento.getName());
                                    }
                                    break;
                                case 3:
                                    if(nProp.equals("tipo-bacteria")) {
                                        tipoBacteria = nodoPropExp.
                                                getTextContent();
                                    } else {
                                        propiedadesCorrecto = false;
                                        Practica1.log.error("Abrir, tipo bacteria inválido");
                                        fireErrorAbriendoExperimento(fichExperimento.getName());
                                    }
                                    break;
                                case 5:
                                    if(nProp.equals("nombre-investigador")) {
                                        nombreInvestigador = nodoPropExp.
                                                getTextContent();
                                    } else {
                                        propiedadesCorrecto = false;
                                        Practica1.log.error("Abrir, nombre investigador inválido");
                                        fireErrorAbriendoExperimento(fichExperimento.getName());
                                    }
                                    break;
                                case 7:
                                    if(nProp.equals("proyecto-investigacion")) {
                                        proyectoInvestigacion = nodoPropExp.
                                                getTextContent();
                                    } else {
                                        propiedadesCorrecto = false;
                                        Practica1.log.error("Abrir, proyecto investigación inválido");
                                        fireErrorAbriendoExperimento(fichExperimento.getName());
                                    }
                                    break;
                                case 9:
                                    if(nProp.equals("num-dias-experimento")) {
                                        numDiasExperimento = Integer.parseInt(nodoPropExp.
                                                getTextContent());
                                    } else {
                                        propiedadesCorrecto = false;
                                        Practica1.log.error("Abrir, proyecto investigación inválido");
                                        fireErrorAbriendoExperimento(fichExperimento.getName());
                                    }
                                    break;
                                case 11:
                                    if(nProp.equals("limite-ali-maxima")) {
                                        limiteAlimentacionMaxima = Integer.parseInt(
                                                nodoPropExp.getTextContent());
                                    } else {
                                        propiedadesCorrecto = false;
                                        Practica1.log.error("Abrir, proyecto investigación inválido");
                                        fireErrorAbriendoExperimento(fichExperimento.getName());
                                    }
                                    break;
                                case 13:
                                    if(nProp.equals("unidades-medida")) {
                                        unidadesMedida = nodoPropExp.
                                                getTextContent();
                                    } else {
                                        propiedadesCorrecto = false;
                                        Practica1.log.error("Abrir, proyecto investigación inválido");
                                        fireErrorAbriendoExperimento(fichExperimento.getName());
                                    }
                                    break;
                            }
                        }
                    }
                } else {
                    propiedadesCorrecto = false;
                    Practica1.log.error("Abrir, fichero inválido");
                    fireErrorAbriendoExperimento(fichExperimento.getName());
                }

                if(!propiedadesCorrecto) {
                    fireErrorAbriendoExperimento(fichExperimento.getName());
                }

                //Creamos el modelo del experimento
                experimento = new LogicExperimento(fichExperimento,
                        nombreExperimento, tipoBacteria, nombreInvestigador,
                        proyectoInvestigacion, numDiasExperimento,
                        limiteAlimentacionMaxima, unidadesMedida, false);

                //Buscamos las poblaciones del experimento
                NodeList lstPoblaciones = elementoCabecera.
                        getElementsByTagName("poblacion");

                //Recorremos las poblaciones y las analizamos
                for(int i=0; i<lstPoblaciones.getLength(); i++) {
                    if(lstPoblaciones.item(i).getNodeType() == Node.ELEMENT_NODE) {
                        try {
                            cargarPoblacion(experimento, lstPoblaciones.item(i));
                        } catch (ExperimentoInvalidoException ex) {
                            fireErrorAbriendoExperimento(fichExperimento.getName());
                        }
                    }
                }
            } catch (ParserConfigurationException ex) {
                Practica1.log.error(Language.getI().getP("ERROR"), ex);
                fireErrorAbriendoExperimento(fichExperimento.getName());
            } catch (SAXException ex) {
                Practica1.log.error(Language.getI().getP("ERROR"), ex);
                fireErrorAbriendoExperimento(fichExperimento.getName());
            } catch (IOException ex) {
                Practica1.log.error(Language.getI().getP("ERROR"), ex);
                fireErrorAbriendoExperimento(fichExperimento.getName());
            }

            xmlDoc = null;
            elementoCabecera= null;
            
            fireAbiertoExperimento(experimento);
        }
        
        /**
        * Carga los datos del XML de la población a un LogicPoblacion
        * @param exp LogicExperimento que contendrá la población
        * @param nPoblacion Nodo XML de la población
        * @throws ExperimentoInvalidoException Lanza un error si el experimento
        * es inváldio
        */
        private void cargarPoblacion(LogicExperimento exp, Node nPoblacion)
                throws ExperimentoInvalidoException {
            String nombre = "";
            int tamanio = 0;
            int temperatura = 0;
            String escala = "";
            String fecha = "";
            String luminosidad = "";
            boolean sincronizar = true;
            JGraficaComidaModel modeloGraficaComida = null;
            ModeloPoblacionTabla modeloPoblacionTabla = null;
            ModeloComentarios modeloComentarios = null;
            SimulacionBacterias modeloSimulacion = null;

            //Obtenemos las propiedades de la población
            NodeList lstValores = nPoblacion.getChildNodes();


            String[] propPoblacion = new String[]{"nombre", "tamaño", "temperatura",
                "escala-temperatura", "fecha", "luminosidad", "sincronizar",
                "alimentacion", "tabla-datos", "comentarios", "simulacion"};


            boolean xmlValido = true;
            if(lstValores.getLength() >= 22) {
                for(int i=1; i<=21; i+= 2) {
                    if(!lstValores.item(i).getNodeName().equals(propPoblacion[(int)i/2])) {
                        xmlValido = false;
                    }
                }
            }

            if(xmlValido) {
                nombre = lstValores.item(1).getTextContent();
                tamanio = Integer.parseInt(lstValores.item(3).getTextContent());
                temperatura = Integer.parseInt(lstValores.item(5).getTextContent());
                escala = lstValores.item(7).getTextContent();
                fecha = lstValores.item(9).getTextContent();
                luminosidad = lstValores.item(11).getTextContent();
                sincronizar = lstValores.item(13).getTextContent().equals("si");
                modeloGraficaComida = cargarAlimentacion(lstValores.item(15));
                modeloPoblacionTabla = cargarTablaDatos(lstValores.item(17));
                modeloComentarios = cargarComentarios(lstValores.item(19));
                if(lstValores.getLength() >= 22) {
                    modeloSimulacion = cargarSimulacion(lstValores.item(21));
                } else {
                    modeloSimulacion = new SimulacionBacterias();
                }
            } else {
                Practica1.log.error("Abrir, error cargar población");
                throw new ExperimentoInvalidoException(
                        Language.getI().getP("FICHERO_INVALIDO"));
            }

            //Creamos un modelo de población genérico
            LogicPoblacion modPoblacion = new LogicPoblacion("PobGenerica", exp);

            //Cargamos los datos
            modPoblacion.setNombrePoblacion(nombre);
            modPoblacion.setTamanioPoblacion(tamanio);
            modPoblacion.setTemperatura(temperatura);
            modPoblacion.setEscalaTemperatura(escala);
            modPoblacion.setFecha(fecha);
            modPoblacion.setLuminosidad(luminosidad);
            modPoblacion.setSincronizarComida(sincronizar);
            modPoblacion.setModeloGraficaComida(modeloGraficaComida);
            modPoblacion.setModeloPoblacionTabla(modeloPoblacionTabla);
            modPoblacion.setModeloComentarios(modeloComentarios);
            modPoblacion.setNumDias(exp.getNumDiasExperimento());
            modPoblacion.setSimulacionBacterias(modeloSimulacion);
            modPoblacion.setModified(false);

            //Añadimos el modelo de la población al experimento
            exp.addPoblacion(modPoblacion);
        }

        private SimulacionBacterias cargarSimulacion(Node nSim) {
            SimulacionBacterias mSim = new SimulacionBacterias();

            //Obtenemos los días de las simulaciones
            NodeList lstValores = nSim.getChildNodes();

            String[] datosSim = new String[]{"num-dias",
                "lado-plato", "tamanio-poblacion", "a-inicial",
                "a-dia-max", "a-maxima", "a-final", "bac-plato", "com-plato"};

            boolean xmlValido = true;
            if(lstValores.getLength() >= 18) {
                for(int i=1; i<=17; i += 2) {
                    if(!lstValores.item(i).getNodeName().
                            equals(datosSim[(int)i/2])) {
                        xmlValido = false;
                    }
                }
            }

            if(xmlValido && lstValores.getLength() != 0) {
                mSim.setNumDias(Integer.parseInt(lstValores.item(1).getTextContent()));
                mSim.setLadoPlato(Integer.parseInt(lstValores.item(3).getTextContent()));
                mSim.setTamanioPoblacion(Integer.parseInt(lstValores.item(5).getTextContent()));
                mSim.setAlimentoInicial(Integer.parseInt(lstValores.item(7).getTextContent()));
                mSim.setDiaMax(Integer.parseInt(lstValores.item(9).getTextContent()));
                mSim.setAlimentoMax(Integer.parseInt(lstValores.item(11).getTextContent()));
                mSim.setAlimentoFinal(Integer.parseInt(lstValores.item(13).getTextContent()));

                int bac[][][] = new int[mSim.getNumDias()][mSim.getLadoPlato()]
                        [mSim.getLadoPlato()];
                int com[][][] = new int[mSim.getNumDias()][mSim.getLadoPlato()]
                        [mSim.getLadoPlato()];

                NodeList nBac = lstValores.item(15).getChildNodes();

                for(int i=1; i<nBac.getLength(); i = i + 2) {
                    String dato = nBac.item(i).getNodeName();
                    String datos[] = dato.split("-");
                    int dia = Integer.parseInt(datos[2]);
                    int lado = Integer.parseInt(datos[3]);

                    String diaBac[] = nBac.item(i).getTextContent().split(";");

                    for(int j=0; j<diaBac.length; j++) {
                        bac[dia][lado][j] = Integer.parseInt(diaBac[j]);
                    }

                }


                NodeList nCom = lstValores.item(17).getChildNodes();

                for(int i=1; i<nCom.getLength(); i = i + 2) {
                    String dato = nCom.item(i).getNodeName();
                    String datos[] = dato.split("-");
                    int dia = Integer.parseInt(datos[2]);
                    int lado = Integer.parseInt(datos[3]);

                    String diaCom[] = nCom.item(i).getTextContent().split(";");

                    for(int j=0; j<diaCom.length; j++) {
                        com[dia][lado][j] = Integer.parseInt(diaCom[j]);
                    }
                }

                mSim.setDatosSimulacion(bac, com);
            }



            return mSim;
        }

        /**
        * Carga el  modelo de los comentarios de la población
        * @param nCom Nodo de los comentarios
        * @return ModeloComentarios de la población
        * @throws ExperimentoInvalidoException Lanza un error si el experimento
        * es inválido
        */
        private ModeloComentarios cargarComentarios(Node nCom) 
                throws ExperimentoInvalidoException {
            ModeloComentarios modeloComentarios = new ModeloComentarios();

            //Obtenemos los comentarios
            NodeList lstComentarios = nCom.getChildNodes();

            for(int i=1; i<lstComentarios.getLength(); i = i + 2) {
                NodeList nComentario = lstComentarios.item(i).getChildNodes();

                String[] datosComentario = new String[]{"fecha", "texto"};

                boolean xmlValido = true;
                if(nComentario.getLength() >= 7) {
                    for(int j=1; j<=3; j += 2) {
                        if(!nComentario.item(j).getNodeName().
                                equals(datosComentario[(int)j/2])) {
                            xmlValido = false;
                        }
                    }
                }

                if(xmlValido) {
                    String fecha = nComentario.item(1).getTextContent();
                    String texto = nComentario.item(3).getTextContent();

                    modeloComentarios.addComentario(new Comentario(texto, fecha));
                } else {
                    Practica1.log.error("Abrir, error cargar comentarios");
                    throw new ExperimentoInvalidoException(
                            Language.getI().getP("FICHERO_INVALIDO"));
                }
            }

            return modeloComentarios;
        }

        /**
        * Carga el modelo de la gŕafica de la población
        * @param nAlim Nodo XML de la alimentación de la gráfica
        * @return ModeloGraficaComida de la población
        * @throws ExperimentoInvalidoException Lanza un error si el experimento es
        * inválido
        */
        private JGraficaComidaModel cargarAlimentacion(Node nAlim)
                throws ExperimentoInvalidoException {
            int aliI = 150;
            int aliM = 150;
            int dia = 15;
            int aliF = 150;

            //Obtenemos las propiedades de la alimentación
            NodeList lstValores = nAlim.getChildNodes();

            String[] datosModeloComida = new String[]{"alimentacion-inicial",
                "alimentacion-maxima", "alimentacion-dia", "alimentacion-final"};


            boolean xmlValido = true;
            if(lstValores.getLength() >= 7) {
                for(int i=1; i<=7; i += 2) {
                    if(!lstValores.item(i).getNodeName().
                            equals(datosModeloComida[(int)i/2])) {
                        xmlValido = false;
                    }
                }
            }

            if(xmlValido) {
                aliI = Integer.parseInt(lstValores.item(1).getTextContent());
                aliM = Integer.parseInt(lstValores.item(3).getTextContent());
                dia = Integer.parseInt(lstValores.item(5).getTextContent());
                aliF = Integer.parseInt(lstValores.item(7).getTextContent());
            } else {
                Practica1.log.error("Abrir, error cargar alimentación");
                throw new ExperimentoInvalidoException(
                        Language.getI().getP("FICHERO_INVALIDO"));
            }


            JGraficaComidaModel modeloGraficaComida = null;
            try {
                modeloGraficaComida = new JGraficaComidaModel(numDiasExperimento,
                        0, limiteAlimentacionMaxima);

                modeloGraficaComida.setAlimentoMax(aliM);
                modeloGraficaComida.setAlimentoInicial(aliI);
                modeloGraficaComida.setDiaMax(dia);
                modeloGraficaComida.setAlimentoFinal(aliF);
            } catch (GraficaComidaIncorrectValueException ex) {
                throw new ExperimentoInvalidoException(ex);
            }

            return modeloGraficaComida;
        }

        /**
        * Carga del modelo de la tabla de la población
        * @param nDat Nodo de los datos de la tabla
        * @return ModeloPoblacionTabla de la población
        * @throws ExperimentoInvalidoException Lanza un error si el experimento
        * es inválido
        */
        private ModeloPoblacionTabla cargarTablaDatos(Node nDat)
                throws ExperimentoInvalidoException {
            //Obtenemos las filas de datos
            NodeList lstValores = nDat.getChildNodes();
            String [][] datos = new String[numDiasExperimento][];
            String []cabeceras = new String[]{
                Language.getI().getP("DIA"),
                Language.getI().getP("TAMANIO_POBLACION"),
                Language.getI().getP("BACTERIAS_MUERTAS"),
                Language.getI().getP("TEMPERATURA"),
                Language.getI().getP("DOSIS_COMIDA")};
            if(lstValores.getLength() != 0) {     
                //Recorremos las filas
                int contDias = 0;
                for(int i=0; i<lstValores.getLength(); i++) {
                    Node nodoDiaTabla = lstValores.item(i);
                    if(nodoDiaTabla.getNodeType() == Node.ELEMENT_NODE) {

                        String nProp = nodoDiaTabla.getNodeName();

                        //Si está el día en orden correcto
                        if(nProp.equals("datos-dia-" + (contDias + 1))) {
                            datos[contDias] = cargarTablaDia(contDias + 1, nodoDiaTabla);
                            contDias++;
                        } else {
                            Practica1.log.error("Abrir, errror cargar tabla datos");
                            throw new ExperimentoInvalidoException(
                                    Language.getI().getP("FICHERO_INVALIDO"));
                        }
                    }
                }
            } else {
                throw new ExperimentoInvalidoException(
                        Language.getI().getP("FICHERO_INVALIDO"));
            }

            return new ModeloPoblacionTabla(datos, cabeceras);
        }

        /**
        * Carga una fila de la tabla
        * @param dia Número del día
        * @param nDia Nodo del día
        * @return String[] Datos de la fila
        * @throws ExperimentoInvalidoException Lanza un error si el experimento
        * es inválido
        */
        private String[] cargarTablaDia(int dia, Node nDia) 
                throws ExperimentoInvalidoException {
            //Obtenemos las propiedades del día
            NodeList lstValores = nDia.getChildNodes();
            String tamanio = "";
            String bacterias = "";
            String temperatura = "";
            String comida = "";    

            String[] datosTabla = new String[]{"tamaño-poblacion", "bacterias-muertas",
            "temperatura", "cantidad-comida"};

            boolean xmlValido = true;
            if(lstValores.getLength() >= 7) {
                for(int i=1; i<=7; i += 2) {
                    if(!lstValores.item(i).getNodeName().equals(datosTabla[(int)i/2])) {
                        xmlValido = false;
                    }
                }
            }

            if(xmlValido) {
                tamanio = lstValores.item(1).getTextContent();
                bacterias = lstValores.item(3).getTextContent();
                temperatura = lstValores.item(5).getTextContent();
                comida = lstValores.item(7).getTextContent();
            } else {
                Practica1.log.error("Abrir, error cargar tabla día");
                throw new ExperimentoInvalidoException(
                        Language.getI().getP("FICHERO_INVALIDO"));
            }

            return new String[]{Integer.toString(dia), 
                    tamanio, bacterias, temperatura, comida};
        }
    }
    
    private static synchronized void fireGuardandoExperimento(
            final LogicExperimento experimento) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                for(ManejoExperimentoListener l : listeners) {
                    l.guardandoExperimento(experimento);
                }
            }
        });
    }
    
    private static synchronized void fireGuardandoExperimento(
            final LogicExperimento experimento,
            final ManejoExperimentoListener l) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                l.guardandoExperimento(experimento);
            }
        });
    }
    
    private static synchronized void fireErrorGuardadoExperimento(
            final LogicExperimento experimento) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                for(ManejoExperimentoListener l : listeners) {
                    l.errorGuardado(experimento);
                }
            }
        });
    }
    
    private static synchronized void fireErrorGuardadoExperimento(
            final LogicExperimento experimento,
            final ManejoExperimentoListener l) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                    l.errorGuardado(experimento);
            }
        });
    }
    
    private static synchronized void fireGuardadoExperimento(
            final LogicExperimento experimento) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                for(ManejoExperimentoListener l : listeners) {
                    l.guardadoExperimento(experimento);
                }
            }
        });
    }
    
    private static synchronized void fireGuardadoExperimento(
            final LogicExperimento experimento,
            final ManejoExperimentoListener l) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                    l.guardadoExperimento(experimento);
            }
        });
    }
    
    private static class GuardarHilo extends Thread {
        private LogicExperimento experimento;
        
        private Document xmlDoc = null;
        private Element elementoCabecera = null;
        private ManejoExperimentoListener listener;
        
        public GuardarHilo(LogicExperimento experimento) {
            this.experimento = experimento;
            listener = null;
        }
        
        public GuardarHilo(LogicExperimento experimento, 
                ManejoExperimentoListener listener) {
            this.experimento = experimento;
            this.listener = listener;
        }
        
        @Override
        public void run() {
            fireGuardandoExperimento(experimento);

            if(listener != null) {
                fireGuardandoExperimento(experimento, listener);
            }
            
            LogicExperimento expGuardar = experimento.clone();

            //Construimos el DOM con las preferencias
            DocumentBuilderFactory dbFactory;
            DocumentBuilder docBuilder;

            try
            {
                dbFactory = DocumentBuilderFactory.newInstance();
                docBuilder = dbFactory.newDocumentBuilder();

                xmlDoc = docBuilder.newDocument();

                //Creamos el elemento que solo contendrá la cabecera
                elementoCabecera = xmlDoc.createElement("experimento");
                xmlDoc.appendChild(elementoCabecera);

                //Agregamos las propiedades del expGuardar
                Element propiedades = xmlDoc.createElement("propiedades-experimento");
                elementoCabecera.appendChild(propiedades);


                Element nombreExp = xmlDoc.createElement("nombre-experimento");
                nombreExp.setTextContent(expGuardar.getNombreExperimento());
                propiedades.appendChild(nombreExp);

                Element tipoBacteria = xmlDoc.createElement("tipo-bacteria");
                tipoBacteria.setTextContent(expGuardar.getTipoBacteria());
                propiedades.appendChild(tipoBacteria);

                Element nombreInvestigador = xmlDoc.createElement("nombre-investigador");
                nombreInvestigador.setTextContent(expGuardar.getNombreInvestigador());
                propiedades.appendChild(nombreInvestigador);

                Element proyectoInvestigacion = xmlDoc.createElement("proyecto-investigacion");
                proyectoInvestigacion.setTextContent(expGuardar.getProyectoInvestigacion());
                propiedades.appendChild(proyectoInvestigacion);

                Element nDias = xmlDoc.createElement("num-dias-experimento");
                nDias.setTextContent(expGuardar.getNumDiasExperimento() + "");
                propiedades.appendChild(nDias);

                Element lMax = xmlDoc.createElement("limite-ali-maxima");
                lMax.setTextContent(expGuardar.getLimiteAlimentacionMaxima() + "");
                propiedades.appendChild(lMax);

                Element uMedida = xmlDoc.createElement("unidades-medida");
                uMedida.setTextContent(expGuardar.getUnidadesMedida());
                propiedades.appendChild(uMedida);

                ArrayList<LogicPoblacion> poblaciones = 
                        expGuardar.getPoblaciones();

                //Guardamos las poblaciones
                for(LogicPoblacion poblacion : poblaciones) {
                    guardarXMLPoblacion(poblacion);
                }

                //Escribimos el XML en el fichero
                OutputStream out = null;
                OutputStreamWriter osw = null;
                StreamResult result;
                DOMSource source;
                Transformer transformer;
                TransformerFactory transformerFactory;

                try {
                    transformerFactory = TransformerFactory.newInstance();
                    transformer = transformerFactory.newTransformer();
                    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

                    source = new DOMSource(xmlDoc);
                    out = new FileOutputStream(
                            expGuardar.getFichExperimento(), false);
                    osw = new OutputStreamWriter(out, "UTF-8");
                    result = new StreamResult(osw);

                    transformer.transform(source, result);
                } catch (TransformerException ex) {
                    Practica1.log.error("Guardar, error");
                    Practica1.log.error(Language.getI().getP("ERROR"), ex);
                    throw new ExperimentoInvalidoException(ex);
                } catch(IOException ex) {
                    Practica1.log.error("Guardar, error");
                    Practica1.log.error(Language.getI().getP("ERROR"), ex);
                    throw new ExperimentoInvalidoException(ex);
                } finally {
                    try {
                        if(osw != null) {
                            osw.close();
                        }
                        if(out != null) {
                            out.close();
                        }
                    } catch(IOException ex) {
                        Practica1.log.error("Guardar, error");
                        Practica1.log.error(Language.getI().getP("ERROR"), ex);
                        throw new ExperimentoInvalidoException(ex);
                    }
                }

                //Le decimos al experimento original que está guardadp
                experimento.setModified(false);

                fireGuardadoExperimento(experimento);
                
                if(listener != null) {
                    fireGuardadoExperimento(experimento, listener);
                }
            } catch (Exception ex) {
                Practica1.log.error(Language.getI().getP("ERROR"), ex);
                fireErrorGuardadoExperimento(experimento);
                
                if(listener != null) {
                    fireErrorGuardadoExperimento(experimento, listener);
                }
            }
        }
        
        /**
        * Carga en el XML para ser guardado la población que se está procesando
        * @param poblacion LogicPoblacion a cargar en el XML
        */
        private void guardarXMLPoblacion(LogicPoblacion poblacion) {
            //Agregamos una población al experimento
            Element elPob = xmlDoc.createElement("poblacion");
            elementoCabecera.appendChild(elPob);

            //Cargamos todas las propiedades de la población
            String nomP = poblacion.getNombrePoblacion();
            String tamP = Integer.toString(poblacion.getTamanioPoblacion());
            String temP = Integer.toString(poblacion.getTemperatura());
            String escP = poblacion.getEscalaTemperatura();
            String fecP = poblacion.getFecha();
            String lumP = poblacion.getLuminosidad();
            String sinP = poblacion.isSincronizarComida() ? "si" : "no";
            JGraficaComidaModel graP = poblacion.getModeloGraficaComida();
            ModeloPoblacionTabla tabP = poblacion.getModeloPoblacionTabla();

            Element nombre = xmlDoc.createElement("nombre");
            nombre.setTextContent(nomP);
            elPob.appendChild(nombre);

            Element tamanio = xmlDoc.createElement("tamaño");
            tamanio.setTextContent(tamP);
            elPob.appendChild(tamanio);

            Element temperatura = xmlDoc.createElement("temperatura");
            temperatura.setTextContent(temP);
            elPob.appendChild(temperatura);

            Element escalaT = xmlDoc.createElement("escala-temperatura");
            escalaT.setTextContent(escP);
            elPob.appendChild(escalaT);

            Element fecha = xmlDoc.createElement("fecha");
            fecha.setTextContent(fecP);
            elPob.appendChild(fecha);

            Element luminosidad = xmlDoc.createElement("luminosidad");
            luminosidad.setTextContent(lumP);
            elPob.appendChild(luminosidad);

            Element sincronizar = xmlDoc.createElement("sincronizar");
            sincronizar.setTextContent(sinP);
            elPob.appendChild(sincronizar);

            String graPAlimI = Integer.toString(graP.getAlimentoInicial());
            String graPAlimM = Integer.toString(graP.getAlimentoMax());
            String graPAlimD = Integer.toString(graP.getDiaMax());
            String graPAlimF = Integer.toString(graP.getAlimentoFinal());

            Element alimentacion = xmlDoc.createElement("alimentacion");
            elPob.appendChild(alimentacion);

            Element alimentoIni = xmlDoc.createElement("alimentacion-inicial");
            alimentoIni.setTextContent(graPAlimI);
            alimentacion.appendChild(alimentoIni);

            Element alimentoMax = xmlDoc.createElement("alimentacion-maxima");
            alimentoMax.setTextContent(graPAlimM);
            alimentacion.appendChild(alimentoMax);

            Element alimentoDia = xmlDoc.createElement("alimentacion-dia");
            alimentoDia.setTextContent(graPAlimD);
            alimentacion.appendChild(alimentoDia);

            Element alimentoFin = xmlDoc.createElement("alimentacion-final");
            alimentoFin.setTextContent(graPAlimF);
            alimentacion.appendChild(alimentoFin);

            Element tabla = xmlDoc.createElement("tabla-datos");
            elPob.appendChild(tabla);
            int numDias = poblacion.getExperimentoPadre().getNumDiasExperimento();
            //Recorro las filas de la tabla
            for(int i=0; i < numDias; i++) {
                Element diaDatos = xmlDoc.createElement("datos-dia-" + (i + 1));
                tabla.appendChild(diaDatos);

                Element tamanioPoblacionDia = xmlDoc.createElement("tamaño-poblacion");
                tamanioPoblacionDia.setTextContent((String)tabP.getValueAt(i, 1));
                diaDatos.appendChild(tamanioPoblacionDia);

                Element bacteriasMuertasDia = xmlDoc.createElement("bacterias-muertas");
                bacteriasMuertasDia.setTextContent((String)tabP.getValueAt(i, 2));
                diaDatos.appendChild(bacteriasMuertasDia);

                Element temperaturaDia = xmlDoc.createElement("temperatura");
                temperaturaDia.setTextContent((String)tabP.getValueAt(i, 3));
                diaDatos.appendChild(temperaturaDia);

                Element cantidadComida = xmlDoc.createElement("cantidad-comida");
                cantidadComida.setTextContent((String)tabP.getValueAt(i, 4));
                diaDatos.appendChild(cantidadComida);
            }

            Element comentarios = xmlDoc.createElement("comentarios");
            elPob.appendChild(comentarios);

            for(Comentario comentario : poblacion.getModeloComentarios().
                    getComentarios()) {
                Element c = xmlDoc.createElement("comentario");
                comentarios.appendChild(c);

                Element fC = xmlDoc.createElement("fecha");
                fC.setTextContent(comentario.getFecha());
                c.appendChild(fC);

                Element tC = xmlDoc.createElement("texto");
                tC.setTextContent(comentario.getTexto());
                c.appendChild(tC);
            }

            Element simulacion = xmlDoc.createElement("simulacion");
            elPob.appendChild(simulacion);
            SimulacionBacterias simulacionBac = poblacion.getSimulacionBacterias();

            if(simulacionBac.getBacteriasPlato() != null &&
                    simulacionBac.getComidaPlato() != null) {
                int aInicial = simulacionBac.getAlimentoInicial();
                int aDiaMax = simulacionBac.getDiaMax();
                int aMax = simulacionBac.getAlimentoMax();
                int aFinal = simulacionBac.getAlimentoFinal();

                int lPlato = simulacionBac.getLadoPlato();
                int nDias = simulacionBac.getNumDias();
                int sBac = simulacionBac.getTamanioPoblacion();
                
                int bPlato[][][] = simulacionBac.getBacteriasPlato();
                int sPlato[][][] = simulacionBac.getComidaPlato();

                Element eND = xmlDoc.createElement("num-dias");
                eND.setTextContent("" + nDias);
                simulacion.appendChild(eND);

                Element eLP = xmlDoc.createElement("lado-plato");
                eLP.setTextContent("" + lPlato);
                simulacion.appendChild(eLP);

                Element eTP = xmlDoc.createElement("tamanio-poblacion");
                eTP.setTextContent("" + sBac);
                simulacion.appendChild(eTP);

                Element eAI = xmlDoc.createElement("a-inicial");
                eAI.setTextContent("" + aInicial);
                simulacion.appendChild(eAI);

                Element eDM = xmlDoc.createElement("a-dia-max");
                eDM.setTextContent("" + aDiaMax);
                simulacion.appendChild(eDM);

                Element eAM = xmlDoc.createElement("a-maxima");
                eAM.setTextContent("" + aMax);
                simulacion.appendChild(eAM);

                Element eAF = xmlDoc.createElement("a-final");
                eAF.setTextContent("" + aFinal);
                simulacion.appendChild(eAF);

                Element eBacPlat = xmlDoc.createElement("bac-plato");
                simulacion.appendChild(eBacPlat);
                for(int i=0; i<nDias; i++) {
                    for(int j=0; j<lPlato; j++) {
                        String nEl = "bac-plato-" + i + "-" + j;
                        StringBuilder cantBacterias = new StringBuilder();
                        for(int k=0; k<lPlato; k++) {
                            cantBacterias.append(bPlato[i][j][k]).append(";");
                        }
                        cantBacterias.deleteCharAt(cantBacterias.length() - 1);

                        Element eDato = xmlDoc.createElement(nEl);
                        eDato.setTextContent(cantBacterias.toString());
                        eBacPlat.appendChild(eDato);
                    }
                }

                Element eComPlat = xmlDoc.createElement("com-plato");
                simulacion.appendChild(eComPlat);
                for(int i=0; i<nDias; i++) {
                    for(int j=0; j<lPlato; j++) {
                        String nEl = "com-plato-" + i + "-" + j;
                        StringBuilder comBacterias = new StringBuilder();
                        for(int k=0; k<lPlato; k++) {
                            comBacterias.append(sPlato[i][j][k]).append(";");
                        }
                        comBacterias.deleteCharAt(comBacterias.length() - 1);

                        Element eDato = xmlDoc.createElement(nEl);
                        eDato.setTextContent(comBacterias.toString());
                        eComPlat.appendChild(eDato);
                    }
                }
            }
        }
    }
}
